package cn.zhaozhiqi.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.zhaozhiqi.constants.Constants;
import cn.zhaozhiqi.dto.GeoSearchResult;
import cn.zhaozhiqi.exception.GlobalException;
import cn.zhaozhiqi.mapper.OrderMapper;
import cn.zhaozhiqi.pojo.PointDto;
import cn.zhaozhiqi.pojo.domain.Order;
import cn.zhaozhiqi.pojo.domain.OrderBill;
import cn.zhaozhiqi.pojo.domain.OrderProfitsharing;
import cn.zhaozhiqi.pojo.driver.dto.DriverSettingDto;
import cn.zhaozhiqi.pojo.dto.app.CreateOrderDto;
import cn.zhaozhiqi.pojo.dto.app.DriverLocationDto;
import cn.zhaozhiqi.pojo.dto.app.OtherFreeDto;
import cn.zhaozhiqi.pojo.order.dto.OrderCreateMQDto;
import cn.zhaozhiqi.pojo.order.dto.PayOrderDto;
import cn.zhaozhiqi.pojo.uaa.vo.LoginVo;
import cn.zhaozhiqi.pojo.vo.app.OrderBillVo;
import cn.zhaozhiqi.remote.api.IBigDataFeignClient;
import cn.zhaozhiqi.remote.api.IDriverFeignClient;
import cn.zhaozhiqi.remote.api.IRuleFeignClient;
import cn.zhaozhiqi.remote.dto.*;
import cn.zhaozhiqi.result.JSONResult;
import cn.zhaozhiqi.service.IOrderBillService;
import cn.zhaozhiqi.service.IOrderProfitsharingService;
import cn.zhaozhiqi.service.IOrderService;
import cn.zhaozhiqi.template.MyRedisTemplate;
import cn.zhaozhiqi.template.TencentMapTemplate;
import cn.zhaozhiqi.utils.AssertUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;






/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zhaozhiqi
 * @since 2024-12-30
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private IRuleFeignClient feignClient;
    @Autowired
    private IOrderBillService orderBillService;
    @Autowired
    private MyRedisTemplate myRedisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private IBigDataFeignClient bigDataFeignClient;
    @Autowired
    private TencentMapTemplate tencentMapTemplate;
    @Autowired
    private IDriverFeignClient driverFeignClient;
    @Autowired
    private IOrderProfitsharingService profitsharingService;

    /**
     * 乘客端支付时显示回显数据
     * @param orderNo
     * @return
     */
    @Override
    public OrderBillVo SendCustomerDetail(String orderNo) {
        OrderBillVo orderBillVo = new OrderBillVo();
        OrderBill bill = orderBillService.getOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderNo, orderNo));
        AssertUtil.isNotNull(bill,"账单拉取异常");
        OrderProfitsharing profitsharing = profitsharingService.getOne(new LambdaQueryWrapper<OrderProfitsharing>()
                .eq(OrderProfitsharing::getOrderNo, orderNo));
        AssertUtil.isNotNull(profitsharing,"分账表拉取异常");
        Order order = getOrder(orderNo);
        BeanUtils.copyProperties(profitsharing, orderBillVo);
        BeanUtils.copyProperties(order, orderBillVo);
        BeanUtils.copyProperties(bill, orderBillVo);
        orderBillVo.setCreateTime(new Date());
        return orderBillVo;
    }

    /**
     * 司机端确认所有费用发送给乘客，创建统一支付单
     * 1.校验订单状态是否已确认，校验是否为此司机订单
     * 2.发送事务消息，同时更改订单状态以及发送消息给pay微服务
     * @param orderNo
     * @return
     */
    @Override
    public Object SendPayorder(String orderNo) {
        Order order = getOrder(orderNo);
        AssertUtil.isNotNull(order,"司机发给乘客的订单不存在");
        AssertUtil.isEquals(order.getStatus(),Constants.OrderStatus.ORDER_STATUS_ENSURE,"订单状态非法");
        AssertUtil.isEquals(order.getDriverId(),StpUtil.getLoginIdAsLong(),"司机非法");

        //需要发送事务消息，要同时将更改订单状态和发送统一支付单对象到MQ，两个事情同时成功
        //1.预修改订单状态
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_NOT_PAY);
        order.setUpdateTime(new Date());
        //2.同时发送事务消息
        PayOrderDto payOrderDto = new PayOrderDto();
        payOrderDto.setAmount(order.getRealOrderAmount());
        payOrderDto.setPayType(Constants.PayType.ALIPAY);
        payOrderDto.setOrderNo(order.getOrderNo());
        payOrderDto.setPayUserId(order.getCustomerId());
        payOrderDto.setExtParams(order.getRemark());
        payOrderDto.setDriverId(order.getDriverId());
        Message<PayOrderDto> message = MessageBuilder.withPayload(payOrderDto).build();
        //发送的参数有：String txProducerGroup, String destination, Message<?> message, Object arg
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                Constants.Mq.TX_ORDER_PAY_GROUP,//事务监听器指定的名字
                Constants.Mq.CREATE_PAY_TOPIC + ":" + Constants.Mq.CREATE_PAY_LAG,//目的地：topic:tags
                message,//发给MQ的消息内容 Message对象
                order//扩展参数 Object;  该参数会传递给事务监听器的arg,这样我们可以在监听器保存order，即监听执行本地事务的状况
        );
        //判断消息是否发送成功
        if (sendResult.getSendStatus() != SendStatus.SEND_OK){
            throw new GlobalException("order服务发送统一支付单的MQ消息失败！");
        }

        return true;
    }



    /**
     * 回显前端——司机端的确认费用页面的详细金额
     * @param orderNo
     * @return
     */
    @Override
    public Object driverGetBill(String orderNo) {
        OrderBillVo orderBillVo = new OrderBillVo();
        OrderBill bill = orderBillService.getOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderNo, orderNo));
        AssertUtil.isNotNull(bill,"账单拉取异常");
        OrderProfitsharing profitsharing = profitsharingService.getOne(new LambdaQueryWrapper<OrderProfitsharing>()
                .eq(OrderProfitsharing::getOrderNo, orderNo));
        AssertUtil.isNotNull(profitsharing,"分账表拉取异常");
        Order order = getOrder(orderNo);
        BeanUtils.copyProperties(profitsharing, orderBillVo);
        BeanUtils.copyProperties(order, orderBillVo);
        BeanUtils.copyProperties(bill, orderBillVo);
        orderBillVo.setCreateTime(new Date());
        return orderBillVo;
    }



    /**
     * 确认司机填写的其他费用的方法
     * 1.查询当前司机的订单，状态和id等满足   2.进行账单和订单的填写，设置状态为确认费用
     * 3.累加到总金额中
     * 4.计算分账比例和分账金额  @TODO
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Object confirmOtherFree(OtherFreeDto dto) {
        Order order = getOrder(dto.getOrderNo());
        //判断订单是否为空，以及当前订单状态
        AssertUtil.isNotNull(order,"非法订单");
        AssertUtil.isEquals(order.getStatus(),Constants.OrderStatus.ORDER_STATUS_COMPLETE_DRIVED,"非法订单");
        //判断订单是否为该司机的
        long driverId = StpUtil.getLoginIdAsLong();
        AssertUtil.isTrue(order.getDriverId()==driverId,"非司机本人订单！");
        OrderBill bill = orderBillService.getById(order.getId());
        //添加这三个费用到bill中,还需要有等时费
        BigDecimal total = order.getRealOrderAmount()
                .add(dto.getOtherAmount())
                .add(dto.getTollAmount())
                .add(dto.getParkingAmount()
                .add(bill.getWaitingAmount()));
        order.setRealOrderAmount(total);
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_ENSURE);
        //进行bill订单的填写
        bill.setTollAmount(dto.getTollAmount());
        bill.setOtherAmount(dto.getOtherAmount());
        bill.setParkingAmount(dto.getParkingAmount());
        bill.setRealOrderAmount(total);
        //@TODO 分账规则
        /**
         * 计算分账比例
         * 1.查询出司机当天取消的订单数量+投诉数量
         * 2.将两个量，包装成对象传入rule微服务，计算出司机当天的分账比例和平台的抽成比例
         * 3。保存分账表
         */
        //1.查询出司机当天取消的订单数量+投诉数量
        JSONResult<DriverSummaryDto> driverSummery = driverFeignClient.getDriverSummery(order.getDriverId());
        //2.将两个量，包装成对象传入rule微服务，计算出司机当天的分账比例和平台的抽成比例
        OrderProfitsharingDto profitsharingDto = new OrderProfitsharingDto();
        DriverSummaryDto summeryData = driverSummery.getData();
        profitsharingDto.setTodayCancel(summeryData.getTodayCancel());
        profitsharingDto.setTodayComplaint(summeryData.getTodayComplaint());
        JSONResult<OrderProfitsharingResultDto> orderProfitsharingDto = feignClient.getOrderProfitsharingDto(profitsharingDto);
        AssertUtil.isTrue(orderProfitsharingDto.isSuccess(),"计算分账异常");
        AssertUtil.isNotNull(orderProfitsharingDto.getData(),"计算分账异常");
        //获取分账比例
        OrderProfitsharingResultDto sharingResultDto = orderProfitsharingDto.getData();
        //封装数据，存储分账表
        OrderProfitsharing prof = new OrderProfitsharing();
        prof.setOrderId(order.getId());
        prof.setOrderNo(order.getOrderNo());
        prof.setOrderAmount(order.getRealOrderAmount());
        prof.setStatus(Constants.Profitsharing.NO_SHARING);
        //设置平台分账金额
        prof.setPlatformIncomeAmount(order.getRealOrderAmount()
                .multiply(sharingResultDto.getPlatformRate()
                        .setScale(2, RoundingMode.HALF_UP)));
        //设置司机分账金额
        prof.setDriverIncomeAmount(order.getRealOrderAmount()
                .multiply(sharingResultDto.getDriverRate())
                .setScale(2,RoundingMode.HALF_UP));
        //设置司机openId
        String loginVoKey = Constants.Redis.LOGINVO + driverId;
        LoginVo loginVo = (LoginVo)redisTemplate.opsForValue().get(loginVoKey);

        prof.setToUserOpenId(loginVo.getOpenId());
        prof.setDeleted(false);
        prof.setCreateTime(new Date());
        prof.setPlatformRate(sharingResultDto.getPlatformRate());
        prof.setDriverRate(sharingResultDto.getDriverRate());

        return super.updateById(order) && orderBillService.updateById(bill) && profitsharingService.save(prof);
    }




    /**
     * 根据传入的订单编号查询订单
     * @param orderNo
     * @return
     */
    public Order getOrder(String orderNo){
        return super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo,orderNo));
    }


    /**
     * 司机结束代驾的方法
     * 1.修改订单状态   2.更改结束时间  3.需要计算实际里程的价格以及实际返程价格
     * 4.将计算好的数据保存到账单和订单
     * @return
     */
    @Override
    @Transactional
    public Object endDriving() {
        //拿到司机当前接到的订单
        Order order = this.currentOrder();
        AssertUtil.isNotNull(order,"司机结束代驾，拉取订单失败");
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_COMPLETE_DRIVED);
        order.setEndTime(new Date());
        //计算司机的实际里程价格
        //1.返回司机路程的每个点的经纬度集合
        JSONResult<List<DriverPointDto>> result = bigDataFeignClient.ComputedRealMile(order.getOrderNo());
        //判断是否返回成功
        AssertUtil.isTrue(result.isSuccess(),"未成功，返回司机路程的每个点的经纬度集合失败");
        AssertUtil.isNotNull(result.getData(),"返回司机路程的每个点的经纬度集合为空");
        //集合内对象的转换
        List<PointDto> collect = result.getData().stream()
                .map(obj -> new PointDto(obj.getLatitude(), obj.getLongitude()))
                .collect(Collectors.toList());
        //进行真实里程的计算
        BigDecimal realMileage = tencentMapTemplate.computedRealMileage(collect);
        //通过之前的ruleFeignClient,将真实里程带入，然后计算真实价格
        JSONResult<ExpectsOrderDto> jsonResult = feignClient.orderComputed(new RuleComputedDto()
                .setCreateTime(order.getCreateTime())
                .setExpectsMileage(realMileage));
        ExpectsOrderDto data = jsonResult.getData();
        AssertUtil.isTrue(result.isSuccess(),"真实价格计算状态码未成功");
        AssertUtil.isNotNull(data,"真实价格计算异常");
        //根据返回值将order和orderBill中的订单内的相关值赋值
        order.setRealMileage(realMileage);
        order.setRealMileage(realMileage);
        //此刻的订单价格只是真实路程价格+返程费
        order.setRealOrderAmount(data.getExpectsOrderAmount());
        //拿到当前账单，进行属性的赋值
        OrderBill bill = orderBillService.getById(order.getId());
        bill.setRealOrderAmount(data.getExpectsOrderAmount());
        bill.setExceedBaseMileage(data.getExceedBaseMileage());
        bill.setExceedBaseMileageAmount(data.getExceedBaseMileageAmount());
        bill.setMileageAmount(data.getMileageAmount());
        bill.setReturnMileage(realMileage);
        bill.setExceedFreeBaseReturnMileage(data.getExceedFreeBaseReturnMileage());
        bill.setReturnAmont(data.getReturnAmont());
        //都保存成功，返回true
        return super.updateById(order) && orderBillService.updateById(bill);
    }

    /**
     * 司机开始代驾
     * 1.修改状态
     * 2.设置开始代驾时间，从后端获取到达代驾点的时间，进行等时费的计算，需要调用rule微服务
     * 如何计算等时费？
     * 写一个feign，通过调用rule来拿取规则,在rule微服务进行计算价格等
     * 超出时间 = 等待总时间 - 免费时间
     * 等时费 = 等时费用 * 超出时间
     * @return
     */
    @Override
    @Transactional
    public Object startDriving() {
        //拿到司机当前接到的订单
        Order order = this.currentOrder();
        AssertUtil.isNotNull(order,"司机开始代驾后拉取订单失败");
        //设置时间和订单状态
        Date now = new Date();
        order.setStartTime(now);
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_START_DRIVING);
        //计算总的等待时间
        long waitTime = DateUtil.between(order.getArriveTime(), now, DateUnit.MINUTE);
        //拿到计算等时费的规则
        WaitTimeComputedDto computedDto = new WaitTimeComputedDto();
        computedDto.setWaitingMinute(waitTime);
        JSONResult<WaitTimeDetailDto> result = feignClient.waitTimeConputed(computedDto);
        OrderBill bill = orderBillService.getById(order.getId());
        BeanUtils.copyProperties(result.getData(),bill);
        //更新账单和订单
        orderBillService.updateById(bill);
        super.updateById(order);
        return true;
    }

    /**
     * 司机到达代驾点方法
     * 需要1.修改订单状态   2.保存到达代驾点的时间
     * @return
     */
    @Override
    public Object driverArrive() {
        //拿到司机当前接到的订单
        Order order = this.currentOrder();
        AssertUtil.isNotNull(order,"司机到达代驾点后拉取订单失败");
        //进行设置
        order.setArriveTime(new Date());
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_ARRIVE);
        super.updateById(order);
        return true;
    }



    /**
     * 乘客端定时拉取redis中的司机实时位置方法
     * @return
     */
    @Override
    public Object pullDriverLocation() {
        //乘客端拿到现在未完成的订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.OrderStatus.ORDER_RUNNING));
        AssertUtil.isNotNull(order,"获取正在进行中的订单失败！");
        String driverCurrentLocation = Constants.Redis.DRIVER_CURRENT_LOCATION + order.getDriverId();
        DriverLocationDto dto = (DriverLocationDto)redisTemplate.opsForValue().get(driverCurrentLocation);
        AssertUtil.isNotNull(dto,"实时获取司机坐标失败！");
        return dto;
    }



    /**
     * 实时更新司机位置坐标，缓存到redis的方法
     * @param dto
     * @return
     */
    @Override
    public Object setDriverGEOInRedis(DriverLocationDto dto) {
        String driverCurrentLocation = Constants.Redis.DRIVER_CURRENT_LOCATION + StpUtil.getLoginIdAsLong();
        redisTemplate.opsForValue().set(driverCurrentLocation,dto);
        return true;
    }

    /**
     * 查询前端司机获取到的订单的状态
     * @param
     * @return
     */
    @Override
    public Order currentOrder() {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.OrderStatus.ORDER_RUNNING));
        return order;
    }

    /**
     * 展示乘客查询的订单
     * @return
     */
    @Override
    public Order getCustomerOrder() {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                //订单状态未接单也要查，这样前端乘客退出后，再进来也可以继续显示相关信息
                .in(Order::getStatus, Constants.OrderStatus.ORDER_STAY));
        return order;
    }



    /**
     * 司机秒杀抢订单
     * 1.先加分布式锁
     * 2.根据orderNo 查询redis
     * 3.查询不到，抢单失败
     * 4.有，绑定司机和订单的关系，同步到数据库- 司机的数据从哪里来??
     * 5.删除redis
     * 6.删除分布式锁
     * @param orderNo
     * @return
     */
    @Override
    @Transactional
    public Object killOrder(String orderNo) {
        //判断一下订单编号是否为空
        AssertUtil.isNotEmpty(orderNo,"订单编号异常，请稍后再试");

        //根据查询redisson加分布式锁
        //1.一个订单一把锁，即使用拼接的key来创建一个锁
        String killOrderKey = Constants.Redis.Kill_ORDER + orderNo;
        RLock lock = redissonClient.getLock(killOrderKey);
        try {
            //2.创建一个分布式锁
            boolean isLock = lock.tryLock();
            AssertUtil.isNotNull(isLock,"加锁失败");
            //3.根据orderNo 查询redis
            String orderKey = Constants.Redis.CREATE_ORDER + orderNo;
            Order order = (Order)redisTemplate.opsForValue().get(orderKey);
            //4.查询不到，抢单失败
            AssertUtil.isNotNull(order,"抢单失败");
            //5.有，绑定司机和订单的关系，同步到数据库.司机信息可以从redis库里面查询,即司机的LoginVo
            //这个方法只会在司机成功抢单的时候调用，所以在这里用satoken拿到司机的id
            long driverId = StpUtil.getLoginIdAsLong();
            String loginVoKey = Constants.Redis.LOGINVO + driverId;
            LoginVo loginVo = (LoginVo)redisTemplate.opsForValue().get(loginVoKey);
            //通过loginVo来对order表中的相应字段赋值
            order.setDriverId(driverId);
            order.setDriverName(loginVo.getNickName());
            order.setDriverPhoto(loginVo.getAvatar());
            order.setDriverPhone("1234567890");
            order.setStatus(Constants.OrderStatus.ORDER_STATUS_ACCEPTED);
            order.setAcceptTime(new Date());
            //先查数据库中的订单是否为待接单状态，如果不是的话，就不能提交修改
            boolean update = super.update(order, new LambdaQueryWrapper<Order>()
                    .eq(Order::getStatus, Constants.OrderStatus.ORDER_STATUS_WAIT));
            AssertUtil.isTrue(update,"订单状态异常，接单失败");
            //删除redis中缓存的订单
            redisTemplate.delete(orderKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            if(lock.isLocked()){//如果订单还是锁住的状态
                lock.unlock();//那就释放锁
            }
        }
        return true;
    }



    /**
     * 订单超时取消方法
     * @param orderNo
     * @return
     */
    @Override
    public Object autoCancelOrder(String orderNo) {
        Order order = super.getOne(
                new LambdaQueryWrapper<Order>()
                .eq(Order::getStatus, Constants.OrderStatus.ORDER_STATUS_WAIT)
                .eq(Order::getOrderNo, orderNo)
        );
        AssertUtil.isNotNull(order,"不能获取订单，订单取消失败");
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_TIMEOUT);
        order.setUpdateTime(new Date());
        super.updateById(order);
        return true;
    }


    /**
     * 创建订单的方法
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Object saveOrder(CreateOrderDto dto) {
        //1.要判断这个乘客是否有其他的订单正在进行中
        long customerId = StpUtil.getLoginIdAsLong();
        Order tmp = super.getOne(new LambdaQueryWrapper<Order>()
                .in(Order::getStatus, Constants.OrderStatus.ORDER_STAY));
        AssertUtil.isNull(tmp,"您有正在进行中的订单，请勿重复下单");
        Date now = new Date();
        //根据规则拿到预估总价以及当前计价规则
        JSONResult<ExpectsOrderDto> result = feignClient.orderComputed(new RuleComputedDto()
                .setExpectMinutes(dto.getExpectMinutes())
                .setCreateTime(now)
                .setExpectsMileage(dto.getExpectsMileage()));
        ExpectsOrderDto data = result.getData();
        //断言计价规则以及预估总价的对象是否为空
        AssertUtil.isNotNull(data,"预估价格异常，请稍后再试");
        //2.创建订单，保存订单；
        Order order = bulidOrder(dto, customerId, now, data);
        //3.根据订单创建账单
        OrderBill orderBill = buliderOrderBill(now, data, order);

        //3.使用geo搜索满足订单范围内的司机
        List<GeoSearchResult> geoSearchResults = myRedisTemplate.searchGeo(Constants.Redis.DRIVER_GEO_KEY,
                Double.valueOf(dto.getStartPlaceLongitude()),
                Double.valueOf(dto.getStartPlaceLatitude()),
                30D,
                5L);
        //判断list是否为空，如果为空需要增加搜索范围。但我们现在暂时断言
        AssertUtil.isNotNull(geoSearchResults,"当前范围内无可用的司机！");
        //循环具有geo坐标的list，list对象中有距离，经纬度，member（id）等
        //4.根据司机的接单规则，筛选司机
        for (GeoSearchResult geoSearchResult : geoSearchResults) {
            //首先需要拿到每个司机的接单规则，然后根据司机的规则进行判断是否将订单推送给司机
            String key = Constants.Redis.DRIVER_SETTING + geoSearchResult.getMember();
            Object settingTmp = redisTemplate.opsForValue().get(key);
            //如果没拿到司机规则，应该跳过,继续循环
            if (settingTmp == null){continue;}
            //将setting对象强转
            DriverSettingDto driverSetting = (DriverSettingDto)settingTmp;
            //如果订单距离司机的距离超过了司机规则内的接单距离，就跳过这个司机继续循环
            if (geoSearchResult.getDistance()>Double.valueOf(driverSetting.getRangeDistance())){
                continue;
            }
            //如果订单起终位置太远，超过了司机接单设置的订单距离，那就跳过这个司机继续循环
            if (dto.getExpectsMileage().compareTo(new BigDecimal(driverSetting.getOrderDistance()))>0){
                continue;
            }
            //包装一个对象，将前端需要的信息包装在内发送给mq，等待driver拉取
            //5.把订单推送给司机
            sendMessageToMQ(dto, order, geoSearchResult);
        }
        //将包装好的订单对象传到redis中，这样我们可以在秒杀的时候查询redis中是否有这个对象来判断司机是否抢到单
        String orderKey = Constants.Redis.CREATE_ORDER + order.getOrderNo();
        redisTemplate.opsForValue().set(orderKey,order);
        return order.getOrderNo();
    }



    /**
     * 将GeoSearchResult对象发送到mq队列
     * @param dto
     * @param order
     * @param geoSearchResult
     */
    private void sendMessageToMQ(CreateOrderDto dto, Order order, GeoSearchResult geoSearchResult) {
        //都通过之后，将这个订单派送给这个司机.前端需要什么结果，就整理一个对象把属性包含在内
        OrderCreateMQDto orderCreateMQDto = new OrderCreateMQDto();
        orderCreateMQDto.setDistance(geoSearchResult.getDistance());
        orderCreateMQDto.setMileage(dto.getExpectsMileage());
        orderCreateMQDto.setFavourFee(order.getFavourAmount());
        orderCreateMQDto.setExpectsFee(order.getExpectsOrderAmount());
        orderCreateMQDto.setFrom(order.getStartPlace());
        orderCreateMQDto.setTo(order.getEndPlace());
        orderCreateMQDto.setOrderNo(order.getOrderNo());
        //通过拼接MQ的key进行传入消息
        String MqKey = Constants.Mq.ORDER_TOPIC +":"+ String.format(Constants.Mq.ORDER_LAG_NAME, geoSearchResult.getMember());
        Message<OrderCreateMQDto> msg = MessageBuilder.withPayload(orderCreateMQDto).build();
        SendResult sendResult = rocketMQTemplate.syncSend(MqKey, msg);
        if (sendResult.getSendStatus() != SendStatus.SEND_OK){
            log.info("发送消息至MQ失败！");
        }
    }

    /**
     * 创建账单
     * @param now
     * @param data
     * @param order
     * @return
     */
    private OrderBill buliderOrderBill(Date now, ExpectsOrderDto data, Order order) {
        OrderBill orderBill = new OrderBill();
        orderBill.setId(order.getId());
        orderBill.setOrderId(order.getId());
        orderBill.setOrderDetail(order.getRemark());
        orderBill.setOrderNo(order.getOrderNo());
        orderBill.setCreateTime(now);
        BeanUtils.copyProperties(data,orderBill);
        orderBillService.save(orderBill);
        return orderBill;
    }

    /**
     * 创建订单
     * @param dto
     * @param customerId
     * @param now
     * @param expectsOrderDto
     * @return
     */
    private Order bulidOrder(CreateOrderDto dto, long customerId,Date now,ExpectsOrderDto expectsOrderDto) {
        Order order = new Order();
        BeanUtils.copyProperties(dto,order);
        order.setOrderNo(IdUtil.getSnowflakeNextIdStr());
        order.setExpectsOrderAmount(expectsOrderDto.getExpectsOrderAmount());
        order.setCreateTime(now);
        order.setFavourAmount(BigDecimal.ZERO);
        StringBuilder sb = new StringBuilder();
        sb.append("乘客：").append(customerId).append("从：").append(dto.getStartPlace())
                .append("到：").append(dto.getEndPlace()).append(",下单时间：")
                .append(now);
        order.setRemark(sb.toString());
        order.setStatus(Constants.OrderStatus.ORDER_STATUS_WAIT);
        /*
        我们可以查询到乘客的相关信息，一般有
        1.直接数据库查 性能差，不太好；
        2.前端传入，比较复杂
        3.从redis中查询  性能较高，我们可以在登录的时候就将相关信息传入redis
         */
        String loginVoKey = Constants.Redis.LOGINVO + customerId;
        LoginVo vo = (LoginVo)redisTemplate.opsForValue().get(loginVoKey);
        order.setCustomerId(customerId);
        order.setCustomerName(vo.getNickName());
        order.setCustomerPhone(vo.getPhone());
        order.setCustomerPhoto(vo.getAvatar());
        super.save(order);
        return order;
    }
}
